//
//  SGSpringGraph.m
//  SpringGraph Test
//
//  Created by Jack Herrington on 5/30/10.
//  Copyright 2010 Myself. All rights reserved.
//

#import "SGSpringGraph.h"

#import "FLAbstractEdge.h"
#import "SGGraphDataProvider.h"
#import "SGEdgeView.h"

@interface SGSpringGraph (PrivateMethods)

- (void)setup;
- (void)drawEdges:(CGContextRef)context;
- (void)drawEdge:(CGContextRef)context from:(UIView *)from to:(UIView *)to color:(UIColor *)color data:(SGEdgeData*)data;
- (void)updateEdge:(UIView<SGEdgeRenderer> *)ev from:(UIView *)from to:(UIView *)to;

@end

@implementation SGSpringGraph

@synthesize lineColor, defaultRepulsionFactor, distinguishedLineColor, delegate;

- (void)setup {
	self.lineColor = [UIColor colorWithWhite:0.8 alpha:1];
	self.distinguishedLineColor = [UIColor blueColor];
	
	nextTagIndex = 1;
	self.repulsionFactor = 0.75;
	self.defaultRepulsionFactor = 100;
	self.multipleTouchEnabled = YES;
	self.userInteractionEnabled = YES;
	self.clipsToBounds = YES;
	draggingNode = nil;
	
	data = [[SGGraphDataProvider alloc] initWithGraph:self];
	repeatingTimer = nil;
	
	UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(performTap:)];
	[self addGestureRecognizer:tapRecognizer];
	[tapRecognizer release];		
	
	UIPanGestureRecognizer* panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(performPan:)];
	panRecognizer.maximumNumberOfTouches = 1;
	panRecognizer.minimumNumberOfTouches = 1;
	[self addGestureRecognizer:panRecognizer];
	[panRecognizer release];		
	
	edgeViewMap = [[NSMutableDictionary alloc] init];
	
	repeatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.001
													  target:self selector:@selector(timerFireMethod:)
													userInfo:nil repeats:YES];
	
	forceDirectedLayout = [[FLForceDirectedLayout alloc] initWithProvider:data];
	
	edgeHost = [[UIView alloc] initWithFrame:self.bounds];
	edgeHost.opaque = YES;
	edgeHost.backgroundColor = [UIColor whiteColor];
	edgeHost.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
	[self addSubview:edgeHost];
}
   
- (void)didMoveToSuperview {
	[self setup];
}

- (void)dealloc {
	[edgeViewMap release];
	[edgeHost release];
	[repeatingTimer release];
	[forceDirectedLayout release];
	[lineColor release];
	[data release];
	[distinguishedLineColor release];
	[super dealloc];
}

-(void)performPan:(UIPanGestureRecognizer*) sender {
	CGPoint loc = [sender locationInView:self];

	if (sender.state == UIGestureRecognizerStateBegan) {
		UIView *sv = [self hitTest:loc withEvent:nil];
		if ( sv != nil && sv != self && sv.tag != 0 ) {
			forceDirectedLayout.dragNode = [data findNode:sv];
			draggingNode = sv;
		}
	}
	
	if (sender.state == UIGestureRecognizerStateEnded) {
		forceDirectedLayout.dragNode = nil;
		draggingNode = nil;
	}

	if ( draggingNode != nil ) {
		draggingNode.frame = CGRectMake( loc.x - ( draggingNode.frame.size.width / 2 ), loc.y - ( draggingNode.frame.size.height / 2 ),
							  draggingNode.frame.size.width, draggingNode.frame.size.height );
		[self refresh];
	}
}

- (void)performTap:(UITapGestureRecognizer *)tapRecognizer {
	CGPoint loc = [tapRecognizer locationInView:self];
	UIView *sv = [self hitTest:loc withEvent:nil];
	if ( sv != self ) {
		[delegate itemTouched:self view:sv];
	}
}

- (void)timerFireMethod:(NSTimer *)timer {
	if ( [forceDirectedLayout tick] ) {
		[self setNeedsDisplay];
	}
}

- (void)setLayoutChanged:(bool)changed {
	[self setNeedsDisplay];
}

- (bool)layoutChanged {
	return false;
}

- (void)setDistinguishedView:(UIView *)item {
	if ( _distinguishedView ) {
		NSMutableDictionary *friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:_distinguishedView.tag]];
		for( NSNumber *friendId in [friendMap allKeys] ) {
			UIView<SGEdgeRenderer> *ev = [friendMap	objectForKey:friendId];
			ev.edgeSelected = false;
			[ev setNeedsDisplay];
		}
	}
	
	_distinguishedView = item;

	NSMutableDictionary *friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:_distinguishedView.tag]];
	for( NSNumber *friendId in [friendMap allKeys] ) {
		UIView<SGEdgeRenderer> *ev = [friendMap	objectForKey:friendId];
		ev.edgeSelected = true;
		[ev setNeedsDisplay];
	}

	[item setNeedsDisplay];
}

- (UIView *)distinguishedView {
	return _distinguishedView;
}

- (float)repulsionFactor {
	return _repulsionFactor;
}

- (void)setRepulsionFactor:(float)f {
	_repulsionFactor = f;
	[self refresh];
}

- (void)refresh {
	[forceDirectedLayout reset];
}

- (void)moveView:(UIView *)view position:(CGPoint)position {
	if ( view != draggingNode ) {
		view.frame = CGRectMake(position.x, position.y,view.frame.size.width,view.frame.size.height);
		
		NSMutableDictionary *friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:view.tag]];
		for( NSNumber *friendId in [friendMap allKeys] ) {
			UIView<SGEdgeRenderer> *ev = [friendMap	objectForKey:friendId];
			[self updateEdge:ev from:view to:[self viewWithTag:[friendId intValue]]];
		}
	}
}

- (SGEdgeData *)connect:(UIView *)from to:(UIView *)to {	
	SGEdgeData *ed = [data link:from to:to];
	
	SGEdgeView *edgeView = [[SGEdgeView alloc] initWithFrame:CGRectMake(0,0,0,0)];
	NSMutableDictionary *friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:from.tag]];
	[friendMap setObject:edgeView forKey:[NSNumber numberWithInt:to.tag]];

	friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:to.tag]];
	[friendMap setObject:edgeView forKey:[NSNumber numberWithInt:from.tag]];
	
	[edgeHost addSubview:edgeView];
	[self updateEdge:edgeView from:from to:to];
	
	return ed;
}

- (void)disconnect:(UIView *)from to:(UIView *)to {
	[data unlink:from to:to];

	NSMutableDictionary *friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:from.tag]];
	UIView<SGEdgeRenderer> *ev = [friendMap objectForKey:[NSNumber numberWithInt:to.tag]];
	[friendMap removeObjectForKey:[NSNumber numberWithInt:to.tag]];
	
	friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:to.tag]];
	[friendMap removeObjectForKey:[NSNumber numberWithInt:from.tag]];
	
	[ev removeFromSuperview];
}

- (void)updateEdge:(UIView<SGEdgeRenderer> *)ev from:(UIView *)from to:(UIView *)to {
	CGPoint m1 = CGPointMake( from.frame.origin.x + ( from.frame.size.width / 2 ), from.frame.origin.y + ( from.frame.size.height / 2 ) );
	CGPoint m2 = CGPointMake( to.frame.origin.x + ( to.frame.size.width / 2 ), to.frame.origin.y + ( to.frame.size.height / 2 ) );
	
	CGPoint origin = CGPointMake( m1.x < m2.x ? m1.x : m2.x, m1.y < m2.y ? m1.y : m2.y );

	CGSize size = CGSizeMake( abs( m1.x - m2.x ), abs( m1.y - m2.y ) );
	size.width = ( size.width < 3 ) ? 3 : size.width;
	size.height = ( size.height < 3 ) ? 3 : size.height;

	ev.frame = CGRectMake( origin.x, origin.y, size.width, size.height );
	if ( m1.x - m2.x == 0 ||  m1.y - m2.y == 0 ) {
		ev.leftOrientation = kSGEdgeOrientationTop;
		ev.rightOrientation = kSGEdgeOrientationBottom;
	} else if ( m1.x < m2.x ) {
		ev.leftOrientation = m1.y < m2.y ? kSGEdgeOrientationTop : kSGEdgeOrientationBottom;
		ev.rightOrientation = m1.y > m2.y ? kSGEdgeOrientationTop : kSGEdgeOrientationBottom;
	} else {
		ev.leftOrientation = m1.y > m2.y ? kSGEdgeOrientationTop : kSGEdgeOrientationBottom;
		ev.rightOrientation = m1.y < m2.y ? kSGEdgeOrientationTop : kSGEdgeOrientationBottom;
	}
}

- (void)didAddSubview:(UIView *)subview {
	if ( edgeHost == subview )
		return;
	
	if ( subview.tag == 0 ) {
		nextTagIndex++;
		subview.tag = nextTagIndex;
	}

	subview.frame = CGRectMake(self.frame.size.width / 2, self.frame.size.height / 2, subview.frame.size.width, subview.frame.size.height );
	
	[data add:subview];
	
	[edgeViewMap setObject:[[NSMutableDictionary alloc] init] forKey:[NSNumber numberWithInt:subview.tag]];
}

- (void)willRemoveSubview:(UIView *)subview {
	if ( edgeHost == subview )
		return;

	NSMutableDictionary *friendMap = [edgeViewMap objectForKey:[NSNumber numberWithInt:subview.tag]];
	for( NSNumber *friendViewId in [friendMap allKeys] ) {
		UIView *link = [friendMap objectForKey:friendViewId];
		[link removeFromSuperview];
		[friendMap removeObjectForKey:[NSNumber numberWithInt:subview.tag]];

		NSMutableDictionary *friendMapToView = [edgeViewMap objectForKey:friendViewId];
		[friendMapToView removeObjectForKey:[NSNumber numberWithInt:subview.tag]];
	}
									
	[data remove:subview];
}

@end
